/*
 * BejeweledRunner.java
 * Runs the GridWorld World with the Pieces for Bejeweled
 * Class 1: Req 5
 * Michelle Chen
 * Period 1
 * 
 */

package bejeweled;

import info.gridworld.world.World;
import info.gridworld.grid.Grid;
import info.gridworld.grid.BoundedGrid;
import info.gridworld.grid.Location;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;
import javax.swing.JOptionPane;

public class BejeweledRunner extends World<Piece>
{
    private Integer level; //determines the level the user is on
    private Integer score;  //stores the user's score
    private Integer lastScore; //score of the last level (stores score so that the new score will have to be 
                           //lastScore +value in scoreReq array)
    private String[] pieceType={"_Shamu","_calvin","_monkey","_penguin","_tigger"};
    private int[] pointsReq ={1000,2000,3000,4000,5000,10000,15000,20000,35000,40000,50000}; //extra points needed from the last level
    private Random rand; //used to generate random number
    private Long timeTemp; //use for countdown timer
    /**
     * Creates a new instance of BejeweledRunner
     * sets the level and score to 0 amd win = false 
     */
    public BejeweledRunner ()
    {
        level = 0; 
        score = 0; 
        lastScore =0; 
        rand = new Random(); 
        makeBoard(); 
        setMessage("BEGIN PLAYING!");
        Date date = new Date(); 
        timeTemp = date.getTime(); //get the initial time
    }
    
    /**
     * Method makeBoard--constructs the board by randomly filling in the 
     * spaces with different color pieces. Checks to make sure that there is
     * not already a 3 in a row. 
     */ 
    private void makeBoard()
    {
        setGrid (new BoundedGrid<Piece>(10,10));
        ArrayList<Location> locList= new ArrayList<Location>(); 
        Grid<Piece> grid = getGrid();
        //add locations to the grid
        for (int x=0; x<grid.getNumRows ();x++)
         {
             for (int y=0; y<grid.getNumCols ();y++)
             {
                 Location loc = new Location(x,y);
                 locList.add (loc); 
             }
         }
        //randomly add pieces 
         String type; 
         for (int i=0;i<locList.size();i++)
         {
             type = pieceType[rand.nextInt(5)];
             add(locList.get(i),new Piece(type));
         }
         //remove the initial dupilicates 
         removeBegin(); 
    }
    
    /**
     * Method removeBegin --removes the chains in the beginning
     * starts the score at zero even after the rearranging of the pieces
     * Uses 1 Logical Operators and 2 Relational Operator (Req 2) 
     */
    private void removeBegin()
    {
        Grid<Piece> gr = getGrid(); 
        for (int x=gr.getNumRows ()-1;x>=0;x--)
         {
             for (int y=gr.getNumCols ()-1; y>=0; y--)
             {
                 Location temp = new Location(x,y);
                 //if a chain is formed, add a new piece
                 if (horiz(temp)||vert(temp))
                 {
                     Piece tempP = changePiece(gr.get(temp)); 
                     remove(temp); 
                     add(temp,tempP); 
                     y++;
                 }
             }
         }
        //keep score at zero at beginning
        score = 0; 
    }
    
    /**
     * method chooses a different piece so that when it is added, it will  not be completely random
     * Uses 1 Relational Operator (Req 2)
     * @return a piece different from current piece
     */
    private Piece changePiece(Piece p)
    {
        String type = p.toString(); 
        for (int i=0; i<5;i++)
        {
            if (pieceType[i].equals (type))
            {
                //returns random pieces that are not the same as this piece
                if (i==4)
                    return new Piece(pieceType[0]);
                else 
                {
                    return new Piece(pieceType[++i]);
                } 
            }
        }
        return null; 
    }
    
    /** 
     * moves the piece to the location that the user would like
     * if the piece forms a chain, the chain is removed 
     * if not, the piece moves back to the original location
     * Uses 3 Logical Operator (Req 2) 
     * Uses if else statements (Req 3)
     * @return true if the piece moves and false if the piece should not move
     */
    private boolean movePiece(Location loc, Location loc2)
    {
        if (vert(loc)||horiz(loc)||vert(loc2)||horiz(loc2))
        {
            Piece temp = (Piece)getGrid().get(loc); 
            //if piece is special, delete according 
            if (temp.interact())
            {
                deleteSuper(); 
            }
            //else just move All the pieces
            else 
                moveAll(loc); 
            return true; 
        }
        return false; 
    }
    
    /**
     * method deletes all special pieces on the board
     * superPieces are all deleted 
     */
    private void deleteSuper()
    {
        for (int x=0;x<getGrid().getNumRows();x++)
        {
            for (int y=0; y<getGrid().getNumCols();y++)
            {
                Location temp = new Location(x,y); 
                Piece tempP = (Piece) getGrid().get(temp); 
                if (tempP.interact())
                {
                    score+=tempP.getPointValue(); 
                    remove(temp); 
                    Piece p = new Piece(pieceType[rand.nextInt (5)]);
                    add(temp,p); 
                }
            }
        }
        
    }
    
    /**
     * check to see if there are 3 or more in a row in vertical direction
     * @param loc
     * @return whether or not the vertical has 3 or more in a row 
     */
    private boolean vert(Location loc)
    {
        Grid gr = getGrid(); 
        return checkConsec(gr,loc,(Piece)gr.get(loc),Location.NORTH,Location.SOUTH);
    }
    
    /**
     * check to see if there are 3 or more in a row in horizontal direction
     * @param loc
     * @return whether or not the horizontal has a 3 or more in a row
     */
    private boolean horiz (Location loc)
    {
        Grid gr = getGrid(); 
        return checkConsec(gr,loc,(Piece)gr.get(loc),Location.LEFT,Location.RIGHT); 
    }
    
    /**
     * Method checks to see if there are consectively more 3
     * Uses a while loop (Req 4)
     * @param grid 
     * @param Location current Location
     * @param piece  current Piece at the location
     * @param dir one direction to check
     * @param dir the opposite direction
     */
    private boolean checkConsec(Grid<Piece> gr, Location loc, Piece p, int dir, int dir2) 
    {
          int consecutive = 0; 
          Location ahead = loc.getAdjacentLocation(dir); 
          while (gr.isValid(ahead)&&gr.get(ahead).equals(p))
          {
              if (consecutive>=1) //if two ahead is the same, then you win
                  return true; 
              consecutive++; 
              ahead = ahead.getAdjacentLocation(dir);           
              if (!gr.isValid(ahead)||!gr.get(ahead).equals(p))
                  break; 
          }
          Location behind = loc.getAdjacentLocation(dir2);
          while (gr.isValid(behind)&&gr.get(behind).equals(p))
          {
              if (consecutive>=1) //if two behind is the same 
                  return true; 
              consecutive++; 
              behind = behind.getAdjacentLocation(dir2);  
              if (!gr.isValid(behind)||!gr.get(behind).equals(p))
                  break; 
          }
          return false; 
    }
    
    /**
     * gets the list of the locations that are in a chain depending on whether or not there 
     * is a chain in the horizontal or vertical direction 
     */
    private ArrayList<Location> completeChain(Location loc)
    {
        ArrayList<Location> list; 
        //determine if it was vertical or horizontal chain
        if (horiz(loc)&&vert(loc))
        {
            list = chain(loc,0,180);
            list.addAll(chain(loc,90,270)); 
        }
        else if (horiz(loc))
        {
            list =  chain(loc,90,270);
        }
        else 
        {
            list = chain(loc,0,180); 
        } 
        return list; 
    }
    
    /**
     * Method moveAll --moves all the other pieces eliminating the three or more in a row
     * Traverses ArrayList using an enhanced for loop which is the same as an iterator (Req 9)
     */
    private void moveAll(Location loc)
    {
        ArrayList<Location> list=completeChain(loc); 
        ArrayList<Location> modified=completeChain(loc); 

        
        //sees if there are more chains associated 
        //if more chains are added, remove all of them
        for (Location x: list)
        {
            ArrayList<Location> temp = completeChain(x); 
            if (temp.size()>=3)
            {
                for (Location tempL: temp)
                {
                    if (!modified.contains(tempL))
                        modified.add(tempL);
                }
            }
        }
        
        //add bonus points for longer chains
        if (list.size()>3)
        {
            score +=(1*(list.size()-3));
        }
        //fill in gaps
        fillGaps(); 
        //add the score points and remove the pieces 
        for (Location i: modified)
        {
            Piece temp = (Piece)getGrid().get(i); 
            if (temp==null)
                fillGaps(); 
            score+=temp.getPointValue(); 
            remove(i); 
        }
        //fill in the gaps that are formed
        fillGaps(); 
    }
    
    /**
     * method finds the chains formed in the particular direction 
     * @param loc the location of the piece
     * @param dir  the first direction desiring the chain connection
     * @param dir2 the opposite direction or whichever direction desired
     * @return a list of all location in the direction specified
     */
    private ArrayList<Location> chain(Location loc, int dir,int dir2)
    {
         ArrayList<Location> original = new ArrayList(); 
         Grid gr= getGrid(); 
         original.add(loc); 
         Location ahead = loc.getAdjacentLocation(dir); 
         Piece p = (Piece)gr.get (loc); 
         while (gr.isValid(ahead)&&gr.get(ahead).equals(p))
          {
              original.add(ahead); 
              ahead = ahead.getAdjacentLocation(dir);           
              if (!gr.isValid(ahead)||!gr.get(ahead).equals(p))
                  break; 
          }
          Location behind = loc.getAdjacentLocation(dir2);
          while (gr.isValid(behind)&&gr.get(behind).equals(p))
          {
              if (!original.contains(behind))
              {
                  original.add(behind);
              } 
              behind = behind.getAdjacentLocation(dir2);  
              if (!gr.isValid(behind)||!gr.get(behind).equals(p))
                  break; 
          }
        return original; 
    }
    
    
    /**
     * Method getScore--returns the current score of the user
     * Uses the Integer Wrapper Class (Req 1) 
     * @return user's score 
     */
    public int getScore()
    {
        return score.intValue (); 
    }

    /** 
     * Method getLevel --returns the level the user is on 
     * 
     * @return user's level 
     */
    public int getLevel()
    {
        return level; 
    }
    
    /** 
     * Method levelUp --determines whether or not the user will move up a level. 
     * 
     * @return true if the user does level up
     * @return false if the user does not have enough points
     */
    private boolean levelUp()
    { 
        int needed = lastScore+pointsReq[getLevel()]; 
        if (score.intValue()>=(needed))
        {
            level++; 
            lastScore = score; 
            return true; 
        }
        return false; 
    }
    
    /**
     * method returns the needed points in order to level up 
     */
    private int neededPts()
    {
        int needed = lastScore+pointsReq[getLevel()];
        return needed-score; 
    }
    private Location clicked; 
    /**
     * Method locationClicked --determines if the user has clicked on the board
     * if time runs out, the user loses or if the user has enough points, he/she wins 
     * Polymorophism by overriding (Req 8) 
     * @return whether or not the user clicked on grid
     */
    @Override
    public boolean locationClicked(Location loc) 
    {
        if (determineWinner())
        {
            return true;
        }
//        time.countDown(); 
        Piece temp  = (Piece)getGrid().get(loc); 
        if (temp!=null)
        {
            if (clicked==null)
            {
                clicked = loc; 
            }
            else 
            {
                //need to figure out the problem with the switching (reseting the locations)
                tempSwitch(loc, clicked);
                clicked = null;
                
            } 
        }
        levelUp();  
        if (remainTime()<0)
        {
            System.out.println("Remain time"+remainTime());
            setMessage("You've Lost! Please play again");
        }
        else if (getLevel()+1==11)
        {
            setMessage("YOU'VE WON!!"); 
             JOptionPane.showMessageDialog(null,"Congratulations! You won with "+getScore()+" points!");
        }
        else 
        {
            setMessage("Your score is currently: "+score.toString ()
                    +"\tYou need: "+neededPts()+" more points to move to the next level."
                    +"\nYou are on level: "+(getLevel()+1) 
                    +".\tYou have:"+ remainTime()+" milliseconds left."); 
        }
        return true; 
    }
    
    /**
     * calculates the remaining time
     * by using Date class 
     * Uses the Long Wrapper Class
     * @return the value of the time remaining 
     */
    private long remainTime()
    {
        Date date = new Date(); 
        Long temp = (date.getTime()/100)-timeTemp/100; 
        Long temp2 = 5000-temp; 
        return temp2.longValue(); 
    }
    
    /** 
     * method checks to see if the move desired is a valid move 
     * @return true if the move is valid and false if not 
     */ 
    private void tempSwitch(Location loc,Location loc2) 
    {
        Grid gr = getGrid();
        ArrayList<Location> adj = getAdj(loc); 
        if (adj.contains(loc2))
        {
            Piece temp = (Piece)gr.get(loc); 
            Piece temp2 = (Piece)gr.get(loc2); 
            remove(loc); 
            remove(loc2); 
            add(loc,temp2);
            add(loc2,temp); 
            //if the switch doesn't form a new chain, switch the pieces back
            if (!movePiece(loc, loc2)) 
            {
                setMessage("Not Valid");
                remove(loc); 
                remove(loc2); 
                add(loc2,temp2);
                add(loc,temp); 
            }
            deleteChains(); 
        }
    }
   
    /**
     * delete the new chains on the board
     *
     */
    private void deleteChains()
    {
     for (int x=getGrid().getNumRows ()-1;x>=0;x--)
            {
                for (int y=0; y<getGrid().getNumRows();y++)
                {
                    Location tempLoc = new Location(x,y);
                    if (horiz(tempLoc)||vert(tempLoc))
                        moveAll(tempLoc);
                }
             }
}
    
    
    /**
     * Method finds the adjacent locations to the north, south, west and east 
     * and returns as an arrayList
     * @param loc
     * @return an arrayList with the adjacent locations 
     */
    private ArrayList<Location> getAdj(Location loc)
    {
        ArrayList<Location> array = new ArrayList<Location>(); 
        int temp = 0 ; 
        while (temp<=360)
        {
            array.add(loc.getAdjacentLocation(temp)); 
            temp+=90; 
        }
        return array; 
    }
    
    
    /**
     * Method determineWinner --determines if the user has finished level 10
     * @return user has won the game
     */
    public boolean determineWinner()
    {
        if (checkNoMoves())
            return false; 
        else if (level==11)
            return true; 
        return false; 
    }
    
    /**
     * checks to see if there are no more valid moves 
     * @return true if there are no more moves and false if there are more moves
     */
    public boolean checkNoMoves()
    {
        for (int x=getGrid().getNumRows()-1; x>=0;x--)
        {
            for (int y=getGrid().getNumCols()-1; y>=0; y--)
            {
                Location temp = new Location (x,y); 
                if (horiz(temp)||vert(temp))
                {
                    return false; 
                }
            }
        }
        return true; 
    }
    
    
    
    /**
     * method fills in the gaps that are formed when the chain is removed
     * if more chains are formed, those are removed
     */
    private void fillGaps()
    {
        for (int x=getGrid().getNumRows ()-1;x>=0;x--)
        {
            for (int y=0; y<getGrid().getNumRows();y++)
            {
                Location temp = new Location(x,y); 
                if (getGrid ().get (temp)==null)
                {
                    if (x==0)
                    {
                        Piece p = new Piece(pieceType[rand.nextInt (5)]);
                        add(temp,p); 
                    }
                    else 
                    {
                        Location temp2= new Location(x-1,y); 
                        if (getGrid().get (temp2)==null)
                        {
                            int randomNum = rand.nextInt(50)+1; 
                            if (randomNum>10&&randomNum<15)
                            {
                                superPiece sp = new superPiece("_star"); 
                                add(temp,sp);
                            }
                            else if (randomNum>15&&randomNum<20)
                            {
                                superPiece sp = new superPiece("_treasure"); 
                                add(temp,sp);
                            }
                            else if (randomNum>40)
                            {
                                superPiece sp = new superPiece(""); 
                                add(temp,sp);
                            }
                            else 
                            {
                                Piece p = new Piece(pieceType[rand.nextInt (5)]);
                                add(temp,p); 
                            }

                        }
                        else 
                        {
                            add(temp,getGrid().get (temp2));
                            remove (temp2); 
                        }
                    }
                }
            }
        }
    }
    
    
    
    /**
     * @param args the command line arguments
     */
    public static void main (String[] args)
    {
        World game = new BejeweledRunner();
        System.setProperty("info.gridworld.gui.selection", "hide");
        System.setProperty ("info.gridworld.gui.tooltips", "hide");
        System.setProperty("info.gridworld.gui.frametitle", "Bejeweled");
        //System.setProperty("info.gridworld.gui.button.gui.run","hide"); 
        //Timer time = new Timer(20); 
        game.show(); 
    }
    
}
